﻿// Copyright 2012 Jack Bulan (agentgeo)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using GemWorkshop.Formats.Nsbtx;

using OpenTK;
using OpenTK.Graphics.OpenGL;

// Based upon "Model Viewer" by pleoNeX
// Pretty much copied

namespace GemWorkshop.Formats.Nsbmd
{
    public partial class ModelViewer : UserControl
    {
        private int mouseX, mouseY;
        private float angleX = 0, angleY = 0, distance = 0.08f;
        private float angleXS = 0, angleYS = 0, distanceS = 0.08f;
        private float x = -1.5f, y = -7.5f, z = 0; // Transate
        private const float STEP = 0.5f;
        private const float ROT_SPEED = 1f;
        private const float ZOOM_FACTOR = 1000f;
        //private float ang = 0.0f;
        //private float dist = 2.5f;
        //private float elev = 0.0f;
        //private float tran = 0.0f;

        //public static float ang = 0.0f;
        //public static float dist = 3.5f;
        //public static float elev = 30f;

        private sBMD0 model;
        private sBTX0 tex;

        private Dictionary<int, int> texturesGL;

        private PolygonMode pm = PolygonMode.Fill;

        public ModelViewer()
        {
            InitializeComponent();
            checkBox1.Checked = false;

            //glControl1.Context.MakeCurrent(glControl1.WindowInfo);
        }

        public ModelViewer(sBMD0 model)
        {
            InitializeComponent();
            this.model = model;
            this.tex = Get_BTX0();

            Get_TexIDS();
            numericUpDown1.Maximum = model.model.mdlData[0].polygon.header.num_objs - 1;
            checkBox1.Checked = false;

            //glControl1.Context.MakeCurrent(glControl1.WindowInfo);
        }

        public ModelViewer(sBMD0 model, sBTX0 tex)
        {
            InitializeComponent();
            this.model = model;
            this.tex = tex;

            Get_TexIDS();
            numericUpDown1.Maximum = model.model.mdlData[0].polygon.header.num_objs - 1;
            checkBox1.Checked = false;

            //glControl1.Context.MakeCurrent(glControl1.WindowInfo);
        }

        private void ModelViewer_Load(object sender, EventArgs e)
        {
            glControl1.Context.MakeCurrent(glControl1.WindowInfo);
            GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            GL.Viewport(glControl1.ClientRectangle);
            glControl1.SwapBuffers();

            //if (model != null)
            //{
                LoadAllTex();
                ProcessBones();
            //}
        }

        private void glControl1_Load(object sender, EventArgs e)
        {
            // NO!
        }

        private void Get_TexIDS()
        {
            for (int i = 0; i < model.model.mdlData[0].material.material.Length; i++)
            {
                sBMD0.Model.ModelData.Material.MatDef mat = model.model.mdlData[0].material.material[i];
                int num_tex, num_pal;
                BTX0Reader.Find_IDs(out num_tex, out num_pal, mat.texName, mat.palName, tex.texture);
                mat.palID = (byte)num_pal;
                mat.texID = (byte)num_tex;
                model.model.mdlData[0].material.material[i] = mat;
            }
        }

        private void LoadAllTex()
        {
            texturesGL = new Dictionary<int, int>();

            for (int i = 0; i < model.model.mdlData[0].material.material.Length; i++)
            {
                sBMD0.Model.ModelData.Material.MatDef mat = (sBMD0.Model.ModelData.Material.MatDef)model.model.mdlData[0].material.material[i];
                texturesGL.Add(i, LoadTextures(mat.texID, mat.palID));
            }
        }

        private void ProcessBones()
        {
            for (int c = 0; c < model.model.mdlData[0].bones.commands.Count; c++)
            {
                sBMD0.Model.ModelData.Bones.Command cmd = model.model.mdlData[0].bones.commands[c];
                switch (cmd.command)
                {
                    case 0x44:
                    case 0x24:
                    case 0x04:
                        int currTex = 0;
                        if (texturesGL.ContainsKey(cmd.parameters[0]))
                            currTex = cmd.parameters[0];
                        else
                            texturesGL.TryGetValue(1, out currTex);

                        model.model.mdlData[0].polygon.display[cmd.parameters[2]].materialAssoc = texturesGL[currTex];
                        model.model.mdlData[0].polygon.display[cmd.parameters[2]].materialID = cmd.parameters[0];
                        break;
                    default:
                        break;
                }
            }
        }

        private int LoadTextures(int num_tex, int num_pal)
        {
            int id = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, id);

            Bitmap bmp = BTX0Reader.GetTexture(tex, num_tex, num_pal);
            System.Drawing.Imaging.BitmapData bmp_data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);

            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bmp_data.Width, bmp_data.Height, 0,
                OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bmp_data.Scan0);

            bmp.UnlockBits(bmp_data);

            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (float)TextureMagFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (float)TextureMinFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (float)TextureWrapMode.Repeat);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (float)TextureWrapMode.Repeat);
            return id;
        }

        private void glControl1_Resize(object sender, EventArgs e)
        {
            GL.Viewport(glControl1.ClientRectangle);
            glControl1.Invalidate();
        }

        private void glControl1_Paint(object sender, PaintEventArgs e)
        {
            Render();
        }

        private void Render()
        {
            GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            GL.PushMatrix();    // For translation and scale

            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();

            GL.Rotate(angleX, 0f, 1f, 0f);
            GL.Rotate(angleY, 0f, 0f, 1f);
            //GL.Rotate(angleZ, 1f, 0f, 0f);
            GL.Scale(distance, distance, distance);
            //GL.Translate(0.0f, 0.0f, -dist);
            //GL.Translate(-tran, 0.0f, 0.0f);
            //GL.Rotate(elev, 1, 0, 0);
            //GL.Rotate(ang, 0, 1, 0);
            GL.Translate(x, y, z);
            //label1.Text = "X: " + x.ToString() + " Y: " + y.ToString() + " Z: " + z.ToString() + "\r\n" +
            //    "AngleX: " + angleX.ToString() + " AngleY: " + angleY.ToString() + " Distance: " + distance.ToString();

            GL.Disable(EnableCap.Texture2D);
            // TODO: Draw box

            // Edges
            DrawEdges();

            GL.Enable(EnableCap.PolygonSmooth);
            GL.Enable(EnableCap.DepthTest);
            GL.Enable(EnableCap.Texture2D);
            GL.Enable(EnableCap.AlphaTest);
            GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
            GL.Enable(EnableCap.Blend);
            GL.AlphaFunc(AlphaFunction.Greater, 0f);
            GL.Disable(EnableCap.CullFace);
            //pm = PolygonMode.Line;
            GL.PolygonMode(MaterialFace.FrontAndBack, pm);

            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (float)TextureWrapMode.Repeat);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (float)TextureWrapMode.Repeat);
            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureEnvMode.Replace);

            if (!checkBox1.Checked) // Check for mode
            {
                for (int i = 0; i < model.model.mdlData[0].polygon.header.num_objs; i++)
                {
                    sBMD0.Model.ModelData.Polygon.Display poly = model.model.mdlData[0].polygon.display[i];
                    if (poly.materialID >= model.model.mdlData[0].material.material.Length)
                        poly.materialID = 0;
                    sBMD0.Model.ModelData.Material.MatDef mat = (sBMD0.Model.ModelData.Material.MatDef)model.model.mdlData[0].material.material[poly.materialID];

                    sBTX0.Texture.TextInfo texInfo = (sBTX0.Texture.TextInfo)tex.texture.texInfo.infoBlock.infoData[mat.texID];

                    GL.BindTexture(TextureTarget.Texture2D, poly.materialAssoc);
                    GL.MatrixMode(MatrixMode.Texture);
                    GL.LoadIdentity();


                    GL.Scale(1.0f / (float)texInfo.width, 1.0f / (float)texInfo.height, 1.0f); // Scale the texture to fill the polygon
                    BMD0Reader.GeometryCommands(poly.commands);
                }
            }
            else
            {
                int i = (int)numericUpDown1.Value;

                sBMD0.Model.ModelData.Polygon.Display poly = model.model.mdlData[0].polygon.display[i];
                if (poly.materialID >= model.model.mdlData[0].material.material.Length)
                    poly.materialID = 0;
                sBMD0.Model.ModelData.Material.MatDef mat = (sBMD0.Model.ModelData.Material.MatDef)model.model.mdlData[0].material.material[poly.materialID];
                sBTX0.Texture.TextInfo texInfo = (sBTX0.Texture.TextInfo)tex.texture.texInfo.infoBlock.infoData[mat.texID];

                GL.BindTexture(TextureTarget.Texture2D, poly.materialAssoc);
                GL.MatrixMode(MatrixMode.Texture);
                GL.LoadIdentity();


                GL.Scale(1.0f / (float)texInfo.width, 1.0f / (float)texInfo.height, 1.0f); // Scale the texture to fill the polygon
                BMD0Reader.GeometryCommands(poly.commands);
            }

            GL.PopMatrix();


            GL.Flush();
            glControl1.SwapBuffers();

        }

        private void DrawEdges()
        {
            GL.Begin(BeginMode.Lines);
            {
                GL.Color3(Color.DarkRed);
                GL.Vertex3(0f, 0f, 0f);
                GL.Vertex3(100f, 0f, 0f);

                GL.Color3(Color.Blue);
                GL.Vertex3(0f, 0f, 0f);
                GL.Vertex3(0f, 100f, 0f);

                GL.Color3(Color.Green);
                GL.Vertex3(0f, 0f, 0f);
                GL.Vertex3(0f, 0f, 100f);
            }
            GL.End();
            GL.Flush();
        }

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            numericUpDown1.Enabled = checkBox1.Checked;
            glControl1.Invalidate();
        }

        private void numericUpDown1_ValueChanged(object sender, EventArgs e)
        {
            glControl1.Invalidate();
        }

        #region Properties
        
        public float Angle
        {
            get { return angleX; }
            set
            {
                angleX = value;
                glControl1.Invalidate();
            }
        }

        public float Distance
        {
            get { return distance; }
            set
            {
                distance = value;
                glControl1.Invalidate();
            }
        }
        
        public float TransX
        {
            get { return x; }
            set
            {
                x = value;
                glControl1.Invalidate();
            }
        }

        public float TransY
        {
            get { return y; }
            set
            {
                y = value;
                glControl1.Invalidate();
            }
        }

        public float TransZ
        {
            get { return z; }
            set
            {
                z = value;
                glControl1.Invalidate();
            }
        }

        private sBTX0 Get_BTX0()
        {
            sBTX0 btx0 = new sBTX0();
            btx0.texture = model.texture;
            //btx0.file = model.filePath;
            btx0.header.offset = new uint[1];
            btx0.header.offset[0] = model.header.offset[1];

            return btx0;
        }

        public void SetTexutre(sBTX0 btx)
        {
            this.tex = btx;

            Get_TexIDS();

            LoadAllTex();
            ProcessBones();
            glControl1.Invalidate();
        }

        public sBMD0 Model
        {
            get { return model; }
        }

        public sBTX0 Texture
        {
            get { return tex; }
        }

        #endregion

        private void glControl1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseX = e.X;
            mouseY = e.Y;
        }

        private void glControl1_MouseUp(object sender, MouseEventArgs e)
        {
            angleXS = angleX;
            angleYS = angleY;
            distanceS = distance;
        }

        private void glControl1_MouseMove(object sender, MouseEventArgs e)
        {
            //MouseEventArgs ev = (e as MouseEventArgs);
            if (e.Button == MouseButtons.Left)
            {
                angleX = angleXS + (e.X - mouseX) * ROT_SPEED;
                angleY = angleYS + (e.Y - mouseY) * ROT_SPEED;
            }
            if (e.Button == MouseButtons.Right)
            {
                distance = (distanceS + (e.Y - mouseY)) / ZOOM_FACTOR;
                if (distance > 3)
                    distance = 3;
                else if (distance <= 0)
                    distance = 2 / ZOOM_FACTOR;
            }
            glControl1.Invalidate();
        }

        private void glControl1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.J)
                x += STEP;
            else if (e.KeyCode == Keys.L)
                x -= STEP;
            else if (e.KeyCode == Keys.K)
                y += STEP;
            else if (e.KeyCode == Keys.I)
                y -= STEP;
            else if (e.KeyCode == Keys.U)
                z += STEP;
            else if (e.KeyCode == Keys.O)
                z -= STEP;
            else if (e.KeyCode == Keys.W)
            {
                if (pm == PolygonMode.Fill)
                    pm = PolygonMode.Line;
                else if (pm == PolygonMode.Line)
                    pm = PolygonMode.Point;
                else
                    pm = PolygonMode.Fill;
            }

            glControl1.Invalidate();
        }

        private void glControl1_KeyUp(object sender, KeyEventArgs e)
        {

        }
    }
}
